package com.zx.security.block.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zx.security.block.filter.JwtAuthenticationTokenFilter;
import com.zx.security.block.handle.AuthenticationEntryPointImpl;
import com.zx.security.block.handle.LogoutSuccessHandlerImpl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

/**
 * <p>
 * description:  security配置 <br>
 * create: 2024-08-20 12:56
 * </p>
 *
 * @author zhou  xun
 */
@Slf4j
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "security")
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class DefaultSecurityConfig {
    /**
     * jackson对象映射器
     */
    private final ObjectMapper objectMapper;

    /**
     * 认证失败处理类
     */
    private final AuthenticationEntryPointImpl authenticationEntryPoint;

    /**
     * 退出处理类
     */
    private final LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 白名单路径列表,列表字段名称要和配置文件一致
     */
    @Getter
    @Setter
    private List<String> whitelistPaths;

    /**
     * Spring Security 安全过滤器链配置<br/>
     * <br/>
     * anyRequest             匹配所有请求路径<br/>
     * access                 SpringEl表达式结果为true时可以访问<br/>
     * anonymous              匿名可以访问<br/>
     * denyAll                用户不能访问<br/>
     * fullyAuthenticated     用户完全认证可以访问（非remember-me下自动登录）<br/>
     * hasAnyAuthority        如果有参数，参数表示权限，则其中任何一个权限可以访问<br/>
     * hasAnyRole             如果有参数，参数表示角色，则其中任何一个角色可以访问<br/>
     * hasAuthority           如果有参数，参数表示权限，则其权限可以访问<br/>
     * hasIpAddress           如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问<br/>
     * hasRole                如果有参数，参数表示角色，则其角色可以访问<br/>
     * permitAll              用户可以任意访问<br/>
     * rememberMe             允许通过remember-me登录的用户访问<br/>
     * authenticated          用户登录后可访问<br/>
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity,
                                                   HandlerMappingIntrospector introspector) throws Exception {
        log.info("whitelist path:{}", objectMapper.writeValueAsString(whitelistPaths));
        // 创建 MVC 请求匹配器构建器
        MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
        httpSecurity
                // 禁用跨站请求伪造（CSRF）保护，适用于API或前后端分离的场景
                .csrf(AbstractHttpConfigurer::disable)
                // 应用CORS（跨源资源共享）配置，使用默认设置
                .cors(Customizer.withDefaults())
                .headers(headersCustomizer -> headersCustomizer
                        // 禁用HTTP响应头中的缓存控制，提高安全性
                        .cacheControl(HeadersConfigurer.CacheControlConfig::disable)
                        // 禁用X-Frame-Options头部，这通常用于防止点击劫持，但在某些情况下可能需要禁用
                        .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
                )
                // 设置会话管理为无状态，适用于基于JWT的认证
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 授权配置
                .authorizeHttpRequests(authorizeHttpRequests -> {
                            // 白名单路径列表不为空时，遍历列表并配置白名单
                            Optional.ofNullable(whitelistPaths)
                                    .filter(Predicate.not(List::isEmpty))
                                    .map(list -> list.stream()
                                            .map(mvcMatcherBuilder::pattern)
                                            .toArray(RequestMatcher[]::new))
                                    .ifPresent(matchers -> authorizeHttpRequests.requestMatchers(matchers).permitAll());
                            //其他所有请求都需要认证
                            authorizeHttpRequests.anyRequest().authenticated();
                        }
                )
                // OAuth2资源服务器配置
                .oauth2ResourceServer(oauth2 ->
                        oauth2.jwt(jwt ->
                                // JWT认证转换器，用于将JWT令牌转换为Spring Security的认证对象
                                jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
                        )
                )
                // 异常处理配置，指定未认证时的处理方式
                .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(authenticationEntryPoint))
                // 注销配置
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
                // 在UsernamePasswordAuthenticationFilter之前添加JWT认证过滤器
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 构建并返回SecurityFilterChain实例
        return httpSecurity.build();
    }

    /**
     * 完全绕过安全过滤器链
     * 这些路径的请求不会经过任何 Spring Security 的过滤和检查（包括认证、授权、CSRF 等）
     *
     * @return {@link WebSecurityCustomizer}
     * @author zhou  xun
     * @since 2025-05-05
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers(
                AntPathRequestMatcher.antMatcher("/webjars/**"),
                AntPathRequestMatcher.antMatcher("/doc.html"),
                AntPathRequestMatcher.antMatcher("/swagger-resources/**"),
                AntPathRequestMatcher.antMatcher("/v3/api-docs/**"),
                AntPathRequestMatcher.antMatcher("/swagger-ui/**"),
                AntPathRequestMatcher.antMatcher("/swagger-ui.html"),
                AntPathRequestMatcher.antMatcher("/"),
                AntPathRequestMatcher.antMatcher("/*.html"),
                AntPathRequestMatcher.antMatcher("/**/*.html"),
                AntPathRequestMatcher.antMatcher("/**/*.css"),
                AntPathRequestMatcher.antMatcher("/**/*.js"),
                AntPathRequestMatcher.antMatcher("/profile/**"),
                AntPathRequestMatcher.antMatcher("/favicon.ico"),
                AntPathRequestMatcher.antMatcher("/druid/**")
        );
    }

    /**
     * 当系统中未创建AuthenticationEventPublisher类型的bean时，创建并返回一个DefaultAuthenticationEventPublisher类型的bean。
     * 此方法用于在应用程序中发布认证事件，当使用自定义的事件发布器时，可以忽略此默认实现。
     *
     * @param delegate 应用程序事件发布器的委托对象，用于实际的事件发布操作。
     * @return 返回一个DefaultAuthenticationEventPublisher实例，用于发布认证事件。
     */
    @Bean
    @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
    DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(ApplicationEventPublisher delegate) {
        return new DefaultAuthenticationEventPublisher(delegate);
    }

    /**
     * 登录接口中使用
     * 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证，
     * 所以需要在SecurityConfig配置中把AuthenticationManager注入容器 ，如果不在这配置bean 就需要在认证实现中注入
     *
     * @param authenticationConfiguration 身份认真配置
     * @return {@link AuthenticationManager}
     * @throws Exception 异常
     * @author zhou  xun
     * @since 2024-03-01
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义JWT Converter
     *
     * @return Converter
     * @see JwtAuthenticationProvider#setJwtAuthenticationConverter(Converter)
     */
    @Bean
    public Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        //  设置权限前缀为空
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
        //  设置从JWT中获取权限的属性名
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}
